//! Plugin support.
//!
//! We need to replace operations related to thread-local variables in
//! `swc_common`.
#![allow(unused)]

use crate::{syntax_pos::Mark, SyntaxContext};
use abi_stable::{
    sabi_trait,
    std_types::{RBox, RVec},
    StableAbi,
};
use anyhow::{Context, Error};
use serde::{de::DeserializeOwned, Serialize};
use std::any::type_name;

#[repr(transparent)]
#[derive(StableAbi)]
pub struct Runtime {
    inner: RuntimeImpl_TO<'static, RBox<()>>,
}

#[cfg(feature = "plugin-mode")]
scoped_tls::scoped_thread_local!(
    /// If this variable is configured, many methods of
    /// `swc_common` will be proxied to this variable.
    pub(crate) static RT: RuntimeImpl_TO<'static, RBox<()>>
);

/// **INTERNAL API**
///
///
/// Don't use this. This is for internal use only.
/// This can be changed without breaking semver version bump.
#[sabi_trait]
pub trait RuntimeImpl {
    /// Emit a structured diagnostic.
    ///
    /// - `db`: Serialized version of Diagnostic which is serialized using
    ///   bincode.
    fn emit_diagnostic(&self, db: RVec<u8>);

    fn fresh_mark(&self, parent: Mark) -> Mark;

    fn parent_mark(&self, mar: Mark) -> Mark;

    fn is_mark_builtin(&self, mark: Mark) -> bool;

    fn set_mark_is_builtin(&self, mark: Mark, is_builtin: bool);

    fn is_mark_descendant_of(&self, mark: Mark, ancestor: Mark) -> bool;

    fn least_ancestor_of_marks(&self, a: Mark, b: Mark) -> Mark;

    fn apply_mark_to_syntax_context_internal(
        &self,
        ctxt: SyntaxContext,
        mark: Mark,
    ) -> SyntaxContext;

    fn remove_mark_of_syntax_context(&self, ctxt: &mut SyntaxContext) -> Mark;

    fn outer_mark_of_syntax_context(&self, ctxt: SyntaxContext) -> Mark;
}
#[cfg(feature = "plugin-mode")]
struct PluginEmitter;

#[cfg(feature = "plugin-mode")]
impl crate::errors::Emitter for PluginEmitter {
    fn emit(&mut self, db: &crate::errors::DiagnosticBuilder<'_>) {
        let bytes: RVec<_> = serialize_for_plugin(&db.diagnostic).unwrap().into();

        RT.with(|rt| rt.emit_diagnostic(bytes))
    }
}

#[cfg(feature = "plugin-mode")]
pub fn with_runtime<F, Ret>(rt: &Runtime, op: F) -> Ret
where
    F: FnOnce() -> Ret,
{
    use crate::errors::{Handler, HANDLER};

    let handler = Handler::with_emitter(true, false, Box::new(PluginEmitter));
    RT.set(&rt.inner, || {
        // We proxy error reporting to the core runtime.
        HANDLER.set(&handler, || op())
    })
}

pub fn serialize_for_plugin<T>(t: &T) -> Result<Vec<u8>, Error>
where
    T: Serialize,
{
    bincode::serialize(&t)
        .with_context(|| format!("failed to serialize `{}` using bincode", type_name::<T>()))
}

pub fn deserialize_for_plugin<T>(bytes: &[u8]) -> Result<T, Error>
where
    T: DeserializeOwned,
{
    bincode::deserialize(bytes)
        .with_context(|| format!("failed to deserialize `{}` using bincode", type_name::<T>()))
}

#[cfg(feature = "plugin-rt")]
struct PluginRt {
    name: String,
}

#[cfg(feature = "plugin-rt")]
impl RuntimeImpl for PluginRt {
    fn emit_diagnostic(&self, db: RVec<u8>) {
        use crate::errors::{Diagnostic, DiagnosticBuilder, HANDLER};

        let diagnostic: Diagnostic =
            deserialize_for_plugin(db.as_slice()).expect("plugin send invalid diagnostic");

        HANDLER.with(|handler| {
            DiagnosticBuilder::new_diagnostic(&handler, diagnostic)
                .note(&format!(
                    "this message is generated by plugin `{}`",
                    &self.name
                ))
                .emit();
        });
    }

    fn fresh_mark(&self, parent: Mark) -> Mark {
        Mark::fresh(parent)
    }

    fn parent_mark(&self, mark: Mark) -> Mark {
        mark.parent()
    }

    fn is_mark_builtin(&self, mark: Mark) -> bool {
        mark.is_builtin()
    }

    fn set_mark_is_builtin(&self, mark: Mark, is_builtin: bool) {
        mark.set_is_builtin(is_builtin)
    }

    fn is_mark_descendant_of(&self, mark: Mark, ancestor: Mark) -> bool {
        mark.is_descendant_of(ancestor)
    }

    fn least_ancestor_of_marks(&self, a: Mark, b: Mark) -> Mark {
        Mark::least_ancestor(a, b)
    }

    fn apply_mark_to_syntax_context_internal(
        &self,
        ctxt: SyntaxContext,
        mark: Mark,
    ) -> SyntaxContext {
        ctxt.apply_mark(mark)
    }

    fn remove_mark_of_syntax_context(&self, ctxt: &mut SyntaxContext) -> Mark {
        ctxt.remove_mark()
    }

    fn outer_mark_of_syntax_context(&self, ctxt: SyntaxContext) -> Mark {
        ctxt.outer()
    }
}

#[cfg(feature = "plugin-rt")]
pub fn get_runtime_for_plugin(plugin_name: String) -> Runtime {
    use abi_stable::erased_types::TD_Opaque;

    let rt = PluginRt { name: plugin_name };

    let rt: RuntimeImpl_TO<'_, RBox<_>> = RuntimeImpl_TO::from_value(rt, TD_Opaque);

    Runtime { inner: rt }
}
